package com.dmw.util;

import com.alibaba.fastjson.JSON;
import cz.mallat.uasparser.UserAgentInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

@Component
public class TokenUtil {

    @Autowired
    private RedisUtil redisUtil;

    private int session_timeout = 60 * 60 * 2; //token有效时长

    private int replacement_protection_timeout = 60 * 60; //token置换保护时长

    private int replacement_delay= 60 * 2; //旧token延迟过期时间

    private String token_prefix = "token:";//token前缀

    /** 获取token默认有效时长 */
    public int getTimeout(){
        return session_timeout;
    }

    /** 获取登录时间的总毫秒数 */
    public long getLogin(String token) throws ParseException {
        //拆分token
        String[] arr = token.split("[-]");

        return new SimpleDateFormat("yyyyMMddHHmmss").parse(arr[3]).getTime();
    }

    /** token是否存在 */
    public boolean exists(String token){
        return redisUtil.exists(token);
    }

    /**
     * 保存token
     * @param token
     * @param user
     */
    public void save(String token, Object user) {
        //判断, token是否以"token:PC-"开头
        if (token.startsWith(token_prefix + "PC-")){
            //PC端登录有效期2小时
            redisUtil.setString(token, JSON.toJSONString(user), session_timeout);
        }else {
            //移动端登录永久有效
            redisUtil.setString(token, JSON.toJSONString(user));
        }
    }

    /**
     * 根据登录账号 保存token，user 和时间
     * @param token
     * @param user
     */
    public void save1(String token,Object user){

        redisUtil.setString(token, JSON.toJSONString(user),session_timeout);
    }

    /**
     * 读取token中的对象
     * @param token
     * @param clazz
     * @return
     */
    public Object load(String token, Class clazz){
        return JSON.parseObject(redisUtil.getString(token), clazz);
    }

    /**
     * 删除token
     * @param token
     */
    public void delete(String token) throws Exception {
        //判断, token在redis中是否存在
        if (!redisUtil.exists(token))
            throw new Exception();
        //删除token
        redisUtil.delete(token);
    }

    /**
     * 生成token
     * @param name 用户账号
     * @param id 用户id
     * @param agent 设备信息
     * @return token<br>
     *     格式:<br>
     *         PC端: 前缀PC-32位的加密name-id-yyyyMMddHHmmss-6位加密的agent<br>
     *         移动端: 前缀MOBLIE-32位的加密name-id-yyyyMMddHHmmss-6位加密的agent
     */
    public String generateToken(String name, Object id, String agent){

        StringBuilder sb = new StringBuilder();

        //添加token前缀
        sb.append(token_prefix);

        //添加设备类型
        try {
            //获取终端类型
            String deviceType = getDeviceType(agent);
            //添加到token
            sb.append(deviceType + "-");
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }

        //添加账号(32位加密)
        sb.append(MD5.getMd5(name, 32) + "-");

        //添加id
        sb.append(id + "-");

        //添加登录时间, 格式yyyyMMddHHmmss
        sb.append(new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "-");

        //添加设备信息(6加密)
        sb.append(MD5.getMd5(agent, 6));

        return sb.toString();
    }

    /**
     * 根据用户登录账号生成token
     * @param name
     * @return
     */
    public String generateToken(String name){
        StringBuilder sb = new StringBuilder();

        //添加name
        sb.append(name + "-");
        //添加账号(32位加密)
        sb.append(MD5.getMd5(name, 32) + "-");
        //添加登录时间, 格式yyyyMMddHHmmss
        sb.append(new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()));

        return sb.toString();

    }

    /**
     * 验证token
     * @param token
     * @param agent 设备信息
     * @param type 验证类型<br>
     *     1: 基本验证. 包含结果1,11,12,13,20
     *     2: 置换验证. 包含结果1,11,12,14
     * @return 结果数值<br>
     *     11: token不存在<br>
     *     12: token格式有误<br>
     *     13: token已超时<br>
     *     14: token仍在保护期<br>
     *     20: 其它token异常<br>
     *     1: token正常
     */
    public int validate(String token, String agent, String type){
        // token不存在
        if (!exists(token)) return 11;
        //拆分token字符串
        String[] tokenDetails = token.split("-");
        //获取token生成时间的总毫秒数
        long genTime = 0;
        try {
           genTime = getLogin(token);
        } catch (ParseException e) {
            e.printStackTrace();
            return 12;
        }
        //计算登录时长
        long passed = Calendar.getInstance().getTimeInMillis() - genTime;
        System.out.println(passed);
        System.out.println(passed / 1000 / 60);
        //判断, 验证类型
        if (type.equals("1")){
            //判断, 是否已超时
            if (passed > session_timeout * 1000) return 13;
            //获取登录设备加密信息
            String agentMD5 = tokenDetails[4];
            //判断, 当前设备是否与登录设备一致
            if(MD5.getMd5(agent, 6).equals(agentMD5)) return 1;
        }else {
            //判断, 是否处于置换保护期内
            if (passed < replacement_protection_timeout * 1000) return 14;
            return 1;
        }

        return 20;
    }

    /**
     * 置换token
     * @param token
     * @param agent 设备信息
     * @param clazz 置换对象的类型
     * @param nameFieldName 账号属性名
     * @param idFiledName id属性名
     * @return
     */
    public String replaceToken(String token, String agent, Class clazz, String nameFieldName, String idFiledName) {
        //获取旧token中存储的用户数据
        Object user = load(token, clazz);
        System.out.println(user);

        //获取旧token有效期（剩余秒数 ）
        long ttl = redisUtil.getExpire(token);
//        System.out.println("ttl:" + ttl);
        //判断token是否仍在有效期内容
//        if (ttl > 0 || ttl == -1) {
            Object name = ""; //用户账号
            Object id = ""; //用户id
            try {
                //获取用户账号
                Method getName = clazz.getDeclaredMethod(nameFieldName);
                name = getName.invoke(user);
                //获取用户id
                Method getId = clazz.getDeclaredMethod(idFiledName);
                id = getId.invoke(user);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            //生成新token
            String newToken = this.generateToken(name.toString(), id.toString(), agent);
            //保存新token
            this.save(newToken, user);
            //设置2分钟后旧token过期，注意手机端由永久有效变为2分钟后失效
            redisUtil.setString(token, JSON.toJSONString(user), replacement_delay);

            return newToken;
//        }

//        return null;
    }

    /**
     * 获取设备类型
     * @param agent
     * @return
     */
    public String getDeviceType(String agent) throws IOException {
        //用户设备信息对象
        UserAgentInfo userAgentInfo = UserAgentUtil.getUasParser().parse(agent);
        //判断, 设备类型是否未知
        if (userAgentInfo.getDeviceType().equals(UserAgentInfo.UNKNOWN)) {
            //判断, 已知设备类型是否为移动端
            if (UserAgentUtil.CheckAgent(agent)) return "MOBILE";
            //已知设备不是移动端, 则为PC端
            else return "PC";
        }
        //判断, 设备类型是否为PC端
        else if (userAgentInfo.getDeviceType().equals("Personal computer")) return "PC";
        //其他类型均识别为移动端
        else return "MOBILE";
    }
}
